/**********
This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)

This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
more details.

You should have received a copy of the GNU Lesser General Public License
along with this library; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
**********/
// "liveMedia"
// Copyright (c) 1996-2018 Live Networks, Inc.  All rights reserved.
// RTP sink for MPEG video (RFC 2250)
// Implementation

#include "include/MPEG1or2VideoRTPSink.hh"
#include "include/MPEG1or2VideoStreamFramer.hh"

MPEG1or2VideoRTPSink::MPEG1or2VideoRTPSink(UsageEnvironment &env, Groupsock *RTPgs)
        : VideoRTPSink(env, RTPgs, 32, 90000, "MPV") {
    fPictureState.temporal_reference = 0;
    fPictureState.picture_coding_type = fPictureState.vector_code_bits = 0;
}

MPEG1or2VideoRTPSink::~MPEG1or2VideoRTPSink() {
}

MPEG1or2VideoRTPSink *
MPEG1or2VideoRTPSink::createNew(UsageEnvironment &env, Groupsock *RTPgs) {
    return new MPEG1or2VideoRTPSink(env, RTPgs);
}

Boolean MPEG1or2VideoRTPSink::sourceIsCompatibleWithUs(MediaSource &source) {
    // Our source must be an appropriate framer:
    return source.isMPEG1or2VideoStreamFramer();
}

Boolean MPEG1or2VideoRTPSink::allowFragmentationAfterStart() const {
    return True;
}

Boolean MPEG1or2VideoRTPSink
::frameCanAppearAfterPacketStart(unsigned char const *frameStart,
                                 unsigned numBytesInFrame) const {
    // A 'frame' (which in this context can mean a header or a slice as well as a
    // complete picture) can appear at other than the first position in a packet
    // in all situations, EXCEPT when it follows the end of (i.e., the last slice
    // of) a picture.  I.e., the headers at the beginning of a picture must
    // appear at the start of a RTP packet.
    if (!fPreviousFrameWasSlice) return True;

    // A slice is already packed into this packet.  We allow this new 'frame'
    // to be packed after it, provided that it is also a slice:
    return numBytesInFrame >= 4
           && frameStart[0] == 0 && frameStart[1] == 0 && frameStart[2] == 1
           && frameStart[3] >= 1 && frameStart[3] <= 0xAF;
}

#define VIDEO_SEQUENCE_HEADER_START_CODE 0x000001B3
#define PICTURE_START_CODE               0x00000100

void MPEG1or2VideoRTPSink
::doSpecialFrameHandling(unsigned fragmentationOffset,
                         unsigned char *frameStart,
                         unsigned numBytesInFrame,
                         struct timeval framePresentationTime,
                         unsigned numRemainingBytes) {
    Boolean thisFrameIsASlice = False; // until we learn otherwise
    if (isFirstFrameInPacket()) {
        fSequenceHeaderPresent = fPacketBeginsSlice = fPacketEndsSlice = False;
    }

    if (fragmentationOffset == 0) {
        // Begin by inspecting the 4-byte code at the start of the frame:
        if (numBytesInFrame < 4) return; // shouldn't happen
        unsigned startCode = (frameStart[0] << 24) | (frameStart[1] << 16)
                             | (frameStart[2] << 8) | frameStart[3];

        if (startCode == VIDEO_SEQUENCE_HEADER_START_CODE) {
            // This is a video sequence header
            fSequenceHeaderPresent = True;
        } else if (startCode == PICTURE_START_CODE) {
            // This is a picture header

            // Record the parameters of this picture:
            if (numBytesInFrame < 8) return; // shouldn't happen
            unsigned next4Bytes = (frameStart[4] << 24) | (frameStart[5] << 16)
                                  | (frameStart[6] << 8) | frameStart[7];
            unsigned char byte8 = numBytesInFrame == 8 ? 0 : frameStart[8];

            fPictureState.temporal_reference = (next4Bytes & 0xFFC00000) >> (32 - 10);
            fPictureState.picture_coding_type = (next4Bytes & 0x00380000) >> (32 - (10 + 3));

            unsigned char FBV, BFC, FFV, FFC;
            FBV = BFC = FFV = FFC = 0;
            switch (fPictureState.picture_coding_type) {
                case 3:
                    FBV = (byte8 & 0x40) >> 6;
                    BFC = (byte8 & 0x38) >> 3;
                    // fall through to:
                case 2:
                    FFV = (next4Bytes & 0x00000004) >> 2;
                    FFC = ((next4Bytes & 0x00000003) << 1) | ((byte8 & 0x80) >> 7);
            }

            fPictureState.vector_code_bits = (FBV << 7) | (BFC << 4) | (FFV << 3) | FFC;
        } else if ((startCode & 0xFFFFFF00) == 0x00000100) {
            unsigned char lastCodeByte = startCode & 0xFF;

            if (lastCodeByte <= 0xAF) {
                // This is (the start of) a slice
                thisFrameIsASlice = True;
            } else {
                // This is probably a GOP header; we don't do anything with this
            }
        } else {
            // The first 4 bytes aren't a code that we recognize.
            envir()
                    << "Warning: MPEG1or2VideoRTPSink::doSpecialFrameHandling saw strange first 4 bytes "
                    << (void *) startCode << ", but we're not a fragment\n";
        }
    } else {
        // We're a fragment (other than the first) of a slice.
        thisFrameIsASlice = True;
    }

    if (thisFrameIsASlice) {
        // This packet begins a slice iff there's no fragmentation offset:
        fPacketBeginsSlice = (fragmentationOffset == 0);

        // This packet also ends a slice iff there are no fragments remaining:
        fPacketEndsSlice = (numRemainingBytes == 0);
    }

    // Set the video-specific header based on the parameters that we've seen.
    // Note that this may get done more than once, if several frames appear
    // in the packet.  That's OK, because this situation happens infrequently,
    // and we want the video-specific header to reflect the most up-to-date
    // information (in particular, from a Picture Header) anyway.
    unsigned videoSpecificHeader =
            // T == 0
            (fPictureState.temporal_reference << 16) |
            // AN == N == 0
            (fSequenceHeaderPresent << 13) |
            (fPacketBeginsSlice << 12) |
            (fPacketEndsSlice << 11) |
            (fPictureState.picture_coding_type << 8) |
            fPictureState.vector_code_bits;
    setSpecialHeaderWord(videoSpecificHeader);

    // Also set the RTP timestamp.  (As above, we do this for each frame
    // in the packet.)
    setTimestamp(framePresentationTime);

    // Set the RTP 'M' (marker) bit iff this frame ends (i.e., is the last
    // slice of) a picture (and there are no fragments remaining).
    // This relies on the source being a "MPEG1or2VideoStreamFramer".
    MPEG1or2VideoStreamFramer *framerSource = (MPEG1or2VideoStreamFramer *) fSource;
    if (framerSource != NULL && framerSource->pictureEndMarker()
        && numRemainingBytes == 0) {
        setMarkerBit();
        framerSource->pictureEndMarker() = False;
    }

    fPreviousFrameWasSlice = thisFrameIsASlice;
}

unsigned MPEG1or2VideoRTPSink::specialHeaderSize() const {
    // There's a 4 byte special video header:
    return 4;
}
